package cmd

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"runtime"
	"sync"
	"time"

	// "gitee.com/quantum-force/of-generic/safe"
	"github.com/chromedp/cdproto/network"
	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
	"github.com/otiai10/gosseract/v2"
)

type (
	Listener        = func(*network.Response)
	ResponseMatcher = func(*network.Response) bool
	SignalChan      = chan struct{}
)

type TaskRunner struct {
	tasks            chromedp.Tasks // 任务序列
	credential       []byte         // credential as cookies
	listeners        []Listener
	blockSig         SignalChan
	doneSig          SignalChan
	errSig           SignalChan
	screenShotBuf    []byte
	ocr              *gosseract.Client
	srMu             *sync.RWMutex
	searchResultInfo *SearchResultInfo
	shopInfo         *ShopCrawlInfo   // data struct for shop cmd crawl info
	reviewInfo       *ReviewCrawlInfo // data struct for review cmd crawl info
}

func NewRunner() *TaskRunner {

	ocr := gosseract.NewClient()
	ocr.SetLanguage("chi_sim")

	tr := &TaskRunner{
		tasks:            preTasks(),
		listeners:        make([]Listener, 0),
		blockSig:         make(SignalChan),
		doneSig:          make(SignalChan),
		errSig:           make(SignalChan),
		ocr:              ocr,
		srMu:             new(sync.RWMutex),
		searchResultInfo: new(SearchResultInfo),
		shopInfo:         new(ShopCrawlInfo),
		reviewInfo:       new(ReviewCrawlInfo),
	}
	// 如果obj实现了Finalizer
	var obj interface{} = tr
	if ob, ok := (obj).(Finalizer); ok {
		runtime.SetFinalizer(ob, func(ob Finalizer) {
			ob.Finalize()
		})
	}

	return tr
}

type Finalizer interface {
	Finalize()
}

func (tr *TaskRunner) Finalize() {
	slog.Infof("finalize called!")
	err := tr.ocr.Close()
	if err != nil {
		slog.Error("*TaskRunner Finalize err=>", err)
	}
}

type BufferHolder interface {
	BufferAddr() *[]byte
	Dump(string) error
	Extract(string) string
}

func (tr *TaskRunner) BufferAddr() *[]byte {
	return &tr.screenShotBuf
}

func (tr *TaskRunner) Dump(file string) error {
	tr.srMu.Lock()
	defer tr.srMu.Unlock()
	return tr.WriteFile(file, bytes.NewReader(tr.screenShotBuf))
}

func (tr *TaskRunner) Extract(file string) string {
	tr.ocr.SetImage(file)
	text, err := tr.ocr.Text()
	if err != nil {
		slog.Errorf("extract err=>", err)
		return ""
	}
	return text
}

func (tr *TaskRunner) Wait(sec int) *TaskRunner {
	tr.tasks = append(tr.tasks, tr.wait(sec))
	return tr
}

func (tr *TaskRunner) BlockUtil(m ResponseMatcher) *TaskRunner {
	tr.AddListener(func(r *network.Response) {
		if m(r) {
			tr.Resume()
		}
	})
	return tr.Block()
}

func (tr *TaskRunner) Block() *TaskRunner {
	tr.tasks = append(tr.tasks, tr.block())
	return tr
}

func (tr *TaskRunner) Resume() {
	tr.blockSig <- struct{}{}
}

func (tr *TaskRunner) AddTasks(tasks chromedp.Tasks) *TaskRunner {
	tr.tasks = append(tr.tasks, tasks)
	return tr
}

func (tr *TaskRunner) Navigate(url string) *TaskRunner {
	tr.tasks = append(tr.tasks, chromedp.Navigate(url))
	return tr
}

func (tr *TaskRunner) NavigateWithReferer(url, referer string) *TaskRunner {
	tr.tasks = append(tr.tasks, tr.navigateWithRefererAction(url, referer))
	return tr
}

func (tr *TaskRunner) navigateWithRefererAction(url, referer string) chromedp.Action {
	return chromedp.ActionFunc(func(ctx context.Context) error {
		_, err := chromedp.RunResponse(ctx,
			chromedp.ActionFunc(func(ctx context.Context) error {
				_, _, _, err := page.Navigate(url).
					WithReferrer(referer).
					Do(ctx)
				slog.Debugf("从[%s]导航到[%s]", referer, url)
				return err
			}),
		)
		return err
	})
}

func (tr *TaskRunner) OutputQRCode() *TaskRunner {
	selector := `#app > div > div.main-content > div > div > div.scan-side > img`
	tr.tasks = append(tr.tasks, chromedp.WaitVisible(selector), chromedp.Sleep(2*time.Second), getQRCode(selector))
	return tr
}

func (tr *TaskRunner) SaveCredential() *TaskRunner {
	tr.tasks = append(tr.tasks, tr.saveCookies())
	return tr
}

func (tr *TaskRunner) DumpCredential() *TaskRunner {
	tr.tasks = append(tr.tasks, tr.saveCookiesAsFile())
	return tr
}

func (tr *TaskRunner) LoadCredential() *TaskRunner {
	tr.tasks = append(tr.tasks, tr.loadCookies())
	return tr
}

func (tr *TaskRunner) LoadCredentialFromFile() *TaskRunner {
	tr.tasks = append(tr.tasks, tr.loadCookiesFromFile())
	return tr
}

func (tr *TaskRunner) _dumpPicBytesToFile(picBytes []byte, file string) (err error) {
	tr.srMu.Lock()
	defer tr.srMu.Unlock()
	err = tr.WriteFile(file, bytes.NewReader(picBytes))
	return
}

func (tr *TaskRunner) AddListener(l Listener) *TaskRunner {
	tr.listeners = append(tr.listeners, l)
	return tr
}

func (tr *TaskRunner) Display() *TaskRunner {
	tr.tasks = append(tr.tasks, chromedp.ActionFunc(func(ctx context.Context) error {
		slog.Infof("credential=>%s", string(tr.credential))
		return nil
	}))
	return tr
}

func (tr *TaskRunner) Run() {
	tr._run(0)
}

func (tr *TaskRunner) RunWithTimeout(timeout time.Duration) {
	tr._run(timeout)
}

func (tr *TaskRunner) _run(timeout time.Duration) {
	go func() {
		ctx, cancel := chromedp.NewExecAllocator(
			context.Background(),
			markChromeDpFlags()...,
		)
		defer cancel()

		if timeout > 0 {
			ctx, cancel = context.WithTimeout(ctx, timeout)
			defer cancel()
		}

		ctx, cancel = chromedp.NewContext(
			ctx,
			chromedp.WithLogf(slog.Debugf),
		)
		defer cancel()

		// ensure that the browser process is started
		if err := chromedp.Run(ctx); err != nil {
			slog.Fatal(err)
			tr.errSig <- struct{}{}
			chromedp.Stop()
			return
		}

		// tr.ContinueForUserInfoSuccess(ctx)

		for _, l := range tr.listeners {
			listenForNetworkEvent(ctx, l)
		}

		err := chromedp.Run(ctx,
			tr.tasks,
		)
		if err != nil {
			slog.Error(err)
			tr.errSig <- struct{}{}
			chromedp.Stop()
			return
		}
		fmt.Println("task is done ,send sig...")
		tr.doneSig <- struct{}{}
	}()
	select {
	case <-tr.doneSig:
		chromedp.Stop()
	case <-tr.errSig:
		chromedp.Stop()
	}

	fmt.Println("receive sig, graceful exit.")
}

// RunResponse is an alternative to Run which can be used with a list of actions
// that trigger a page navigation, such as clicking on a link or button.
//
// RunResponse will run the actions and block until a page loads, returning the
// HTTP response information for its HTML document. This can be useful to wait
// for the page to be ready, or to catch 404 status codes, for example.
//
// Note that if the actions trigger multiple navigations, only the first is
// used. And if the actions trigger no navigations at all, RunResponse will
// block until the context is cancelled. '
// 非常有用
//  func RunResponse(ctx context.Context, actions ...Action) (*network.Response, error) {

func listenForNetworkEvent(ctx context.Context, l Listener) {
	chromedp.ListenTarget(ctx, func(ev interface{}) {
		switch ev := ev.(type) {
		case *network.EventResponseReceived:
			resp := ev.Response
			l(resp)
		}
		// other needed network Event
	})
}

func (tr *TaskRunner) dumpToFile(file string, v any) (err error) {
	data, err := json.Marshal(v)
	if err != nil {
		fmt.Printf("Error: %s", err)
		return
	}
	tr.srMu.Lock()
	defer tr.srMu.Unlock()
	err = tr.WriteFile(file, bytes.NewReader(data))
	return
}

func (tr *TaskRunner) Reset() *TaskRunner {
	tr.tasks = append(tr.tasks,
		chromedp.EmulateReset(),
	)
	return tr
}
